基于MSE云原生网关实现前端灰度

前端灰度发布与可监控、可回滚策略相结合,形成了一个强大的系统稳定性保障机制。通过这三个策略的协同工作,可以确保前端应用在不断迭代和更新的同时,保持高性能和高稳定性,本文介绍如何通过配置MSE云原生网关实现前端灰度。

端到端全链路灰度实现

在微服务场景中,应用间的调用是随机的。当您部署的Spring Cloud应用或Dubbo应用存在升级版本时,可能会导致无法将具有一定特征的流量路由到应用的目标版本。通过MSE提供的云原生网关实现全链路灰度,配合前端灰度方案,即可实现端到端的全链路灰度

前端用户每一次的请求都经过云原生网关,经过权限系统验证后,所有请求的Cookie中都带上了用户的唯一标识,比如 userid: 001

说明

网关挂载了一个 frontend-gray插件,通过配置插件规则,将灰度流量进行映射并传递。

image

前提条件

Ingress类型服务实现前端灰度

说明

通过MSE Ingress访问容器服务,请参见MSE Ingress访问容器服务

步骤1:使用容器服务部署应用

应用部署的具体操作,请参见创建无状态工作负载Deployment

frontend-base.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: frontend
  namespace: default
spec:
  replicas: 1
  selector:
    matchLabels:
      app: frontend
  template:
    metadata:
      labels:
        app: frontend
    spec:
      containers:
        - image: 'registry.cn-hangzhou.aliyuncs.com/mse-demo-hz/user-gray:base'
          imagePullPolicy: Always
          name: frontend
          resources: {}
---
apiVersion: v1
kind: Service
metadata:
  name: frontend-base-svc
  namespace: default
spec:
  ports:
    - port: 80
      protocol: TCP
      targetPort: 80
  selector:
    app: frontend
  type: ClusterIP

---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  labels:
    ingress-controller: mse
  namespace: default
  name: frontend-base-ingress
spec:
  ingressClassName: mse
  rules:
    - http:
        paths:
          - backend:
              service:
                name: frontend-base-svc
                port:
                  number: 80
            path: /
            pathType: Prefix
frontend-gray.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: frontend-gray
  namespace: default
spec:
  replicas: 1
  selector:
    matchLabels:
      app: frontend-gray
  template:
    metadata:
      labels:
        app: frontend-gray
    spec:
      containers:
        - image: 'registry.cn-hangzhou.aliyuncs.com/mse-demo-hz/user-gray:gray'
          imagePullPolicy: Always
          name: frontend-gray
          resources: {}
---
apiVersion: v1
kind: Service
metadata:
  name: frontend-gray-svc
  namespace: default
spec:
  ports:
    - port: 80
      protocol: TCP
      targetPort: 80
  selector:
    app: frontend-gray
  type: ClusterIP

---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  labels:
    ingress-controller: mse
  annotations:
    nginx.ingress.kubernetes.io/canary: 'true'
    nginx.ingress.kubernetes.io/canary-by-header: x-higress-tag
    nginx.ingress.kubernetes.io/canary-by-header-value: gray
  name: frontend-gray-ingress
  namespace: default
spec:
  ingressClassName: mse
  rules:
    - http:
        paths:
          - backend:
              service:
                name: frontend-gray-svc
                port:
                  number: 80
            path: /
            pathType: Prefix

步骤2:MSE控制台配置灰度插件

  1. 登录MSE网关管理控制台,并在顶部菜单栏选择地域。

  2. 在左侧导航栏,选择云原生网关 > 网关列表,单击目标网关名称。

  3. 在左侧导航栏,单击插件市场

  4. 插件市场页面,选择自定义,然后单击创建插件

  5. 创建插件面板,填写插件参数信息,单击确定,等待插件发布成功。

    参数

    插件名称

    frontend-gray

    插件描述

    frontend-gray

    wasm实现语言

    TinyGo

    wasm文件

    上传下载的 frontend-gray(main.wasm)文件

    插件执行阶段

    默认阶段

    插件执行优先级

    100

  6. 插件发布成功之后,单击创建的插件frontend-gray选项卡,选择插件配置 > 实例级插件规则

  7. 实例级插件规则页面配置如下规则,详细配置可参见配置规则

    grayKey: userid
    rules:
      - name: beta-user
        grayKeyValue:
          - "00000002"
          - "00000003"
    baseDeployment:
      version: base
    grayDeployments:
      - name: beta-user
        version: gray
        enabled: true

    69e020c14764255fb5ec59f544220d2c

步骤3:结果验证

  1. 登录容器服务控制台,在左侧导航栏选择集群,在集群列表页面单击所创建的容器服务集群,进入集群详情页,在左侧导航栏选择网络 > 路由,查看公网访问端点。

    image

  2. 访问公网端点8.136.xxx.xxx,登录 admin/ice 账号,访问主版本,用户ID为 00000001。

    image

  3. 访问公网端点8.136.xxx.xxx,登录普通用户user/ice,访问灰度版本,用户ID为 00000002。

    image

ACK容器服务实现前端灰度

步骤1:使用容器服务部署应用

应用部署的具体操作,请参见创建无状态工作负载Deployment

frontend-base.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: frontend
  namespace: default
spec:
  replicas: 1
  selector:
    matchLabels:
      app: frontend
  template:
    metadata:
      labels:
        app: frontend
    spec:
      containers:
        - image: 'registry.cn-hangzhou.aliyuncs.com/mse-demo-hz/user-gray:base'
          imagePullPolicy: Always
          name: frontend
          resources: {}
---
apiVersion: v1
kind: Service
metadata:
  name: frontend-base-svc
  namespace: default
spec:
  ports:
    - port: 80
      protocol: TCP
      targetPort: 80
  selector:
    app: frontend
  type: ClusterIP
frontend-gray.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: frontend-gray
  namespace: default
spec:
  replicas: 1
  selector:
    matchLabels:
      app: frontend-gray
  template:
    metadata:
      labels:
        app: frontend-gray
    spec:
      containers:
        - image: 'registry.cn-hangzhou.aliyuncs.com/mse-demo-hz/user-gray:gray'
          imagePullPolicy: Always
          name: frontend-gray
          resources: {}
---
apiVersion: v1
kind: Service
metadata:
  name: frontend-gray-svc
  namespace: default
spec:
  ports:
    - port: 80
      protocol: TCP
      targetPort: 80
  selector:
    app: frontend-gray
  type: ClusterIP

为ACK命名空间中的应用开启MSE微服务治理

  1. 登录MSE治理中心控制台,并在顶部菜单栏选择地域。

  2. 在左侧导航栏,选择治理中心 > 应用治理

  3. 应用列表页面,单击ACK应用接入

  4. ACK应用接入对话框中,进行配置,配置完成后,单击确定

    image

    配置项

    说明

    集群类型

    选择ACK集群ACK Serverless集群ACS集群

    说明

    如果您尚未授权容器服务调用微服务引擎,则需要单击请授权进行授权。

    集群名称/ID

    选择接入MSE微服务治理的集群名称/ID,可通过关键词搜索。

    ack-onepilot

    显示ack-onepilot接入状态。

    • 如果您未安装ack-onepilot,单击ack-onepilot右侧的点击安装,安装完成后状态会显示为已安装

    • 如果您使用子账号接入,提示没有权限使用时,您可以登录容器服务管理控制台进入目标集群,然后单击运维管理>组件管理,找到ack-onepilot,点击安装。

    说明
    • 该步骤接入的组件为ack-onepilot,您可以登录容器服务管理控制台进入目标集群,然后单击运维管理>组件管理查看详情。

    • ack-onepilot安装后会自动注入探针,可能会导致应用启动耗时增加(10s内)。

    接入类型

    选择命名空间接入

    容器集群命名空间

    选择容器集群命名空间

    治理命名空间

    选择治理命名空间。在对应命名空间下重新部署现有应用或新创建的应用,均会接入到MSE微服务治理中。关于命名空间的相关信息,请参见微服务命名空间管理

为单个应用开启MSE微服务治理

  1. 登录MSE治理中心控制台,并在顶部菜单栏选择地域。

  2. 在左侧导航栏,选择治理中心 > 应用治理

  3. 应用列表页面,单击ACK应用接入

  4. ACK应用接入对话框中,进行配置,配置完成后,单击确定

    image

    配置项

    说明

    集群类型

    选择ACK集群ACK Serverless集群ACS集群

    说明

    如果您尚未授权容器服务调用微服务引擎,则需要单击请授权进行授权。

    集群名称/ID

    选择接入MSE微服务治理的集群名称/ID,可通过关键词搜索。

    ack-onepilot

    显示ack-onepilot接入状态。

    • 如果您未安装ack-onepilot,单击ack-onepilot右侧的点击安装,安装完成后状态会显示为已安装

    • 如果您使用子账号接入,提示没有权限使用时,您可以登录容器服务管理控制台进入目标集群,然后单击运维管理>组件管理,找到ack-onepilot,点击安装。

    说明
    • 该步骤接入的组件为ack-onepilot,您可以登录容器服务管理控制台进入目标集群,然后单击运维管理>组件管理查看详情。

    • ack-onepilot安装后会自动注入探针,可能会导致应用启动耗时增加(10s内)。

    接入类型

    选择单个应用接入

    接入步骤

    按照接入步骤进行操作。

    Step 1:进入集群工作负载-无状态应用页面,切换到应用的命名空间下

    Step 2:找到所接入的应用,点击「查看Yaml」

    Step 3:按以下格式编辑Labels,完成后点击「更新」

    spec:
      template:
        metadata:
          labels:
            # 填写“on”表示开启接入,需加上双引号
            msePilotAutoEnable: "on"
            # 填写接入到的治理命名空间,值不存在可自动新建
            mseNamespace: 202401
            # 填写接入MSE的实际应用名称,需加上双引号
            msePilotCreateAppName: "your-deployment-name"

步骤2:MSE控制台配置灰度插件

  1. 登录MSE网关管理控制台,并在顶部菜单栏选择地域。

  2. 在左侧导航栏,选择云原生网关 > 网关列表,单击目标网关名称。

  3. 在左侧导航栏,单击插件市场

  4. 插件市场页面,选择自定义,然后单击创建插件

  5. 创建插件面板,填写插件参数信息,单击确定,等待插件发布成功。

    参数

    插件名称

    frontend-gray

    插件描述

    frontend-gray

    wasm实现语言

    TinyGo

    wasm文件

    上传下载的 frontend-gray(main.wasm)文件

    插件执行阶段

    默认阶段

    插件执行优先级

    100

  6. 插件发布成功之后,单击创建的插件frontend-gray选项卡,选择插件配置 > 实例级插件规则

  7. 实例级插件规则页面配置如下规则,详细配置可参见配置规则

    grayKey: userid
    rules:
      - name: beta-user
        grayKeyValue:
          - "00000002"
          - "00000003"
    baseDeployment:
      version: base
    grayDeployments:
      - name: beta-user
        version: gray
        enabled: true

    69e020c14764255fb5ec59f544220d2c

步骤3:添加网关服务来源

  1. 登录MSE网关管理控制台,并在顶部菜单栏选择地域。

  2. 在左侧导航栏,选择云原生网关 > 网关列表,单击目标网关名称。

  3. 在左侧导航栏,选择路由管理,然后选择来源页签。

  4. 单击创建来源。在创建来源面板,配置来源类型容器服务ACK/ACK Serverless集群选择所创建的容器服务集群, 然后单击确定

    image

步骤4:创建服务

  1. 登录MSE网关管理控制台,并在顶部菜单栏选择地域。

  2. 在左侧导航栏选择云原生网关 > 网关列表

  3. 网关列表页面,单击目标网关名称。

  4. 在左侧导航栏,选择路由管理,然后选择服务页签。

  5. 单击创建服务。在创建服务面板,配置服务相关参数,然后单击确定

    image

步骤5:创建base路由

  1. 登录MSE网关管理控制台,并在顶部菜单栏选择地域。

  2. 在左侧导航栏,选择云原生网关 > 网关列表,单击目标网关名称。

  3. 在左侧导航栏,单击路由管理,然后在路由页签单击创建路由

  4. 创建路由页面,配置相关项,然后单击保存

    image

步骤6:创建gray路由

  1. 登录MSE网关管理控制台,并在顶部菜单栏选择地域。

  2. 在左侧导航栏,选择云原生网关 > 网关列表,单击目标网关名称。

  3. 在左侧导航栏,单击路由管理,然后在路由页签单击创建路由

  4. 创建路由页面,配置相关配置项,然后单击保存

    image

    说明

    gray代表灰度版本,和frontend-gray配置中 deploy.gray.version对应

步骤7:结果验证

  1. 登录MSE网关管理控制台,并在顶部菜单栏选择地域。

  2. 在左侧导航栏,选择云原生网关 > 网关列表,单击目标网关名称。

  3. 在网关详情页面找到网关入口,查看公网访问端点。

    image

  4. 访问公网端点8.136.xxx.xxx,登录 admin/ice 账号,访问主版本,用户ID为 00000001。

    image

  5. 访问公网端点8.136.xxx.xxx,登录普通用户user/ice,访问灰度版本,用户ID为 00000002。

    image

ECS类型服务实现前端灰度

步骤1:ECS分别部署两个前端应用

基线应用地址为:120.79.137.243:80

灰度应用地址为:120.79.137.243:8081

步骤2:创建服务

  1. 登录MSE网关管理控制台,并在顶部菜单栏选择地域。

  2. 在左侧导航栏选择云原生网关 > 网关列表

  3. 网关列表页面,单击目标网关名称。

  4. 在左侧导航栏,选择路由管理,然后选择服务页签。

  5. 单击创建服务。在创建服务面板,服务来源为固定地址,配置服务相关参数,然后单击确定

    image

    image

步骤3:创建base路由

  1. 登录MSE网关管理控制台,并在顶部菜单栏选择地域。

  2. 在左侧导航栏,选择云原生网关 > 网关列表,单击目标网关名称。

  3. 在左侧导航栏,单击路由管理,然后在路由页签单击创建路由

  4. 创建路由页面,配置相关配置项,然后单击保存

    image

步骤4:创建gray路由

  1. 登录MSE网关管理控制台,并在顶部菜单栏选择地域。

  2. 在左侧导航栏,选择云原生网关 > 网关列表,单击目标网关名称。

  3. 在左侧导航栏,单击路由管理,然后在路由页签单击创建路由

  4. 创建路由页面,配置相关项,然后单击保存

    说明

    gray代表灰度版本,和frontend-gray配置中 deploy.gray.version对应

    image

步骤5:MSE控制台配置灰度插件

  1. 登录MSE网关管理控制台,并在顶部菜单栏选择地域。

  2. 在左侧导航栏,选择云原生网关 > 网关列表,单击目标网关名称。

  3. 在左侧导航栏,单击插件市场

  4. 插件市场页面,选择自定义,然后单击创建插件

  5. 创建插件面板,填写插件参数信息,单击确定,等待插件发布成功。

    参数

    插件名称

    frontend-gray

    插件描述

    frontend-gray

    wasm实现语言

    TinyGo

    wasm文件

    上传下载的 frontend-gray(main.wasm)文件

    插件执行阶段

    默认阶段

    插件执行优先级

    100

  6. 插件发布成功之后,单击创建的插件frontend-gray选项卡,选择插件配置 > 实例级插件规则

  7. 实例级插件规则页面配置如下规则,详细配置可参见配置规则

    grayKey: userid
    rules:
      - name: beta-user
        grayKeyValue:
          - "00000002"
          - "00000003"
    baseDeployment:
      version: base
    grayDeployments:
      - name: beta-user
        version: gray
        enabled: true

    image

步骤6:结果验证

  1. 登录MSE网关管理控制台,并在顶部菜单栏选择地域。

  2. 在左侧导航栏,选择云原生网关 > 网关列表,单击目标网关名称。

  3. 在网关详情页面找到网关入口,查看公网访问端点。

    image

  4. 访问公网端点8.136.xxx.xxx,登录 admin/ice 账号,访问主版本,用户ID为 00000001。

    image

  5. 访问公网端点8.136.xxx.xxx,登录普通用户user/ice,访问灰度版本,用户ID为 00000002。

    image

CDN/OSS类型服务实现前端灰度

步骤1:OSS文件规划

- app1 # 应用
  - dev # dev版本
    - index.html
    - js
      ...
    - css
      ...
    - images
      ...
  - 0.0.1  # 001版本
    - index.html
    - js
      ...
    - css
      ...
    - images
      ...
  - 0.0.2  # 002版本
    - index.html
    - js
      ...
    - css
      ...
    - images
      ...
- app2
  - dev
    - index.html
    - js
      ...
    - css
      ...
    - images
      ...
  - 0.0.1
    - index.html
    - js
      ...
    - css
      ...
    - images
      ...
  - 0.0.2
    - index.html
    - js
      ...
    - css
      ...
    - images
      ...

步骤2:创建服务

  1. 登录MSE网关管理控制台,并在顶部菜单栏选择地域。

  2. 在左侧导航栏选择云原生网关 > 网关列表

  3. 网关列表页面,单击目标网关名称。

  4. 在左侧导航栏,选择路由管理,然后选择服务页签。

  5. 单击创建服务。在创建服务面板,服务来源为DNS域名,域名列表填写OSS地址,然后单击确定

    image

    重要

    如果OSS的和网关在同一个Region,建议填写OSS的内网地址,如果不在一个Region,请填写公网地址。

步骤3:创建路由

  1. 登录MSE网关管理控制台,并在顶部菜单栏选择地域。

  2. 在左侧导航栏,选择云原生网关 > 网关列表,单击目标网关名称。

  3. 在左侧导航栏,单击路由管理,然后在路由页签单击创建路由

  4. 创建路由页面,配置相关项,然后单击保存

    image

步骤4:MSE控制台配置灰度插件

  1. 登录MSE网关管理控制台,并在顶部菜单栏选择地域。

  2. 在左侧导航栏,选择云原生网关 > 网关列表,单击目标网关名称。

  3. 在左侧导航栏,单击插件市场

  4. 插件市场页面,选择自定义,然后单击创建插件

  5. 创建插件面板,填写插件参数信息,单击确定,等待插件发布成功。

    参数

    插件名称

    frontend-gray

    插件描述

    frontend-gray

    wasm实现语言

    TinyGo

    wasm文件

    上传下载的 frontend-gray(main.wasm)文件

    插件执行阶段

    默认阶段

    插件执行优先级

    100

  6. 插件发布成功之后,单击创建的插件frontend-gray选项卡,选择插件配置 > 实例级插件规则

  7. 实例级插件规则页面配置如下规则,详细配置可参见配置规则。

    grayKey: userid
    rules:
      - name: beta-user
        grayKeyValue:
          - "00000002"
          - "00000003"
    rewrite:
      host: xx.oss-cn-shanghai.aliyuncs.com ##OSS 地址
      indexRouting:
        "/app1": "/project-a/app1/{version}/index.html" #首页(html)路径重写
      fileRouting:
         "/app1": "/project-a/app1/{version}" #资源(css/js/images)路径重写
    baseDeployment:
      version: dev
    grayDeployments:
      - name: beta-user
        version: 0.0.1
        enabled: true

步骤5:结果验证

  1. 登录MSE网关管理控制台,并在顶部菜单栏选择地域。

  2. 在左侧导航栏,选择云原生网关 > 网关列表,单击目标网关名称。

  3. 在网关详情页面找到网关入口,查看公网访问端点。

    image

  4. 访问公网端点8.136.xxx.xxx,登录 admin/ice 账号,访问主版本,用户ID为 00000001。

    image

  5. 访问公网端点8.136.xxx.xxx,登录普通用户user/ice,访问灰度版本,用户ID为 00000002。

    image

FAQ

前端灰度是否还可以配置重写策略

  • 如果服务来源是非CDN/OSS ,可以搭配重写策略生效。

  • 如果服务来源是CDN/OSS的前端灰度是不可以搭配重写策略,因为相关的重写策略在frontend-gray灰度插件中实现了,如果配置重写策略,会发生冲突,返回403等一些异常情况。

是否可以往HTML首页注入一些全局变量

  • 可以在html<head> 标签中(一般是CSS样式等属性),或者<body>标签的头部和尾部注入一些全局JavaScript脚本。

  • 通过injection往HTML首页注入代码,可以在head标签注入代码,也可以在body标签的firstlast位置注入代码。

grayKey: userid
rules:
- name: inner-user
  grayKeyValue:
  - '00000001'
  - '00000005'
baseDeployment:
  version: base
grayDeployments:
  - name: beta-user
    version: gray
    enabled: true
    weight: 80
injection:
  head: 
    - <script>console.log('Header')</script>
  body:
    first:
      - <script>console.log('hello world before')</script>
      - <script>console.log('hello world before1')</script>
    last:
      - <script>console.log('hello world after')</script>
      - <script>console.log('hello world after2')</script>

灰度版本生效时机?

假设A用户现在的前端版本是0.0.1, 这时候发布前端版本 0.0.2, 并且A客户命中了灰度规则,是否能立即生效?

是不会立即生效,出于下面几点原因考虑:

  • 如果发布版本需要实时生效,这时候就需要由后端来动态控制版本。首先无法实现前后端发布解耦,其次页面的稳定性强依赖这个接口,无法做CDN加速。

  • 假设版本能够实时生效,客户在使用某个功能的时候,可能出现上一秒还在使用某个按钮,下一秒这个按钮就不见了的情况。体验非常糟糕。

什么时机刷新页面?

  • 一般网站是有设置Session超时重新登录,一段时间没有访问后,需重新登录页面。

  • 用户通过登录页面登录到应用中。

所以,在登录页面的时候,需要刷新页面,以获取最新的灰度信息。前端登录示例代码如下:

async function handleLogin(values: LoginParams) {
    try {
      const result = await login(values);
      if (result.success) {
        message.success('登录成功!');
        await updateUserInfo();
        const urlParams = new URL(window.location.href).searchParams;
  	    window.location.href = `${urlParams.get('redirect') || '/'}`;
        return;
      }
      console.log(result);
      // 如果失败去设置用户错误信息,显示提示信息
      setLoginResult(result);
    } catch (error) {
      message.error('登录失败,请重试!');
      console.log(error);
    }
  }